#include "driver/gpio.h"
#include "driver/ledc.h"
#include "esp_timer.h"
#include "api_map.h"
#include "board_v1_1_map.h"
#include "esp_lvgl_port.h"
#include "esp_log.h"

#include "config.h"

static const char *TAG = "LCD";
extern void *lcd_init(void);
extern void lcd_pannel_on(uint8_t on);
extern uint32_t lcd_saver_time;
extern uint32_t btn_tick;

struct lcd_status
{
    uint32_t lcd_cur_on : 1;
    uint32_t lcd_cur_bri : 7;
    uint32_t lcd_bri : 7;
    uint32_t lcd_on : 1;
    uint32_t lcd_last_on : 1;
    uint32_t lcd_bri_min : 7;
    uint32_t lcd_bri_max : 7;
} lcd_state;

void lcd_bri_on(uint8_t on)
{
    if (on > 1)
    {
        lcd_state.lcd_on = lcd_state.lcd_last_on;
        btn_tick = esp_log_timestamp();
    }
    else
    {
        lcd_state.lcd_last_on = lcd_state.lcd_on;
        lcd_state.lcd_on = on;
    }
}

void lcd_set_bri(uint8_t bri)
{
    if (bri > 100)
        bri = 100;
    config_set("lcd_bri", 1, &bri);
    lcd_state.lcd_bri = bri;
}

static void timer_cb(void *arg)
{
    uint8_t process = 0;

    if (lcd_state.lcd_cur_on != lcd_state.lcd_on)
    {
        lcd_state.lcd_cur_on = lcd_state.lcd_on;
        // lcd_pannel_on(lcd_state.lcd_cur_on);
        process = 1;
    }

    if (lcd_state.lcd_cur_bri != lcd_state.lcd_bri)
    {
        lcd_state.lcd_cur_bri = lcd_state.lcd_bri;
        process = 1;
    }

    if (!process)
        return;

    process = lcd_state.lcd_cur_bri;

    if (process < lcd_state.lcd_bri_min)
        process = lcd_state.lcd_bri_min;
    else if (process > lcd_state.lcd_bri_max)
        process = lcd_state.lcd_bri_max;

    uint16_t duty = 255 * process / 100;
    if (!lcd_state.lcd_cur_on)
        duty = 0;

    if (ledc_set_duty(LEDC_LOW_SPEED_MODE, 1, duty) != ESP_OK)
    {
        printf("\e[31m[pwm] Failed to set pwm duty\e[0m\n");
        return;
    }

    if (ledc_update_duty(LEDC_LOW_SPEED_MODE, 1) != ESP_OK)
    {
        printf("\e[31m[pwm] Failed to update pwm duty\e[0m\n");
        return;
    }
    printf("change to lcd brightness %d(%d)\r\n", duty, process);
}

static void lcd_bri_init(void)
{
    ledc_timer_config_t ledc_timer2 = {
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .timer_num = LEDC_TIMER_1,
        .duty_resolution = 8,
        .freq_hz = 100 * 1000, // 70 kHz
        .clk_cfg = LEDC_AUTO_CLK};

    ledc_channel_config_t lcd_channel = {
        .speed_mode = LEDC_LOW_SPEED_MODE,
        .timer_sel = LEDC_TIMER_1,
        .channel = (ledc_channel_t)1,
        .gpio_num = CONFIG_LCD_SPI_BLK_PIN,
        .duty = 0,
        .flags = {
            .output_invert = 1,
        },
        .hpoint = 0};

    if (ledc_timer_config(&ledc_timer2) != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to config pwm timer2");
        return;
    }

    if (ledc_channel_config(&lcd_channel) != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to config pwm channel1");
        return;
    }

    const esp_timer_create_args_t timer_args = {
        .callback = &timer_cb,
        .name = "lcd_bri"};
    esp_timer_handle_t sys_state_timer = NULL;
    ESP_ERROR_CHECK(esp_timer_create(&timer_args, &sys_state_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(sys_state_timer, 100 * 1000));
}
static void lcd_get_config(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    char *str = (char *)server->data;
    uint16_t len = 0;
    len += sprintf(&str[len], "{");
    len += sprintf(&str[len], "\"api\":\"%s\",", "lcd_get_config");
    len += sprintf(&str[len], "\"data\":{");
    len += sprintf(&str[len], "\"enable\": %s,", lcd_state.lcd_on ? "true" : "false");
    len += sprintf(&str[len], "\"brightness\": %d,", lcd_state.lcd_bri);
    len += sprintf(&str[len], "\"min\": %d,", lcd_state.lcd_bri_min);
    len += sprintf(&str[len], "\"max\": %d,", lcd_state.lcd_bri_max);
    len += sprintf(&str[len], "\"saver_time\": %lu,", lcd_saver_time / 1000);
    len--;
    len += sprintf(&str[len], "}}");
    frame->payload = server->data;
    frame->len = len;
}

static void lcd_update_config(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    static const char *const keys[] = {"enable", "brightness", "min", "max", "saver_time"};
#define keys_num sizeof(keys) / sizeof(keys[0])
    cJSON *objs[keys_num] = {0};
    for (uint8_t i = 0; i < keys_num; i++)
    {
        if (!cJSON_HasObjectItem(data, keys[i]))
            return ws_reply_status(server, frame, "lcd_update_config", -1);
        objs[i] = cJSON_GetObjectItem(data, keys[i]);
    }

    if (!cJSON_IsBool(objs[0]) || !cJSON_IsNumber(objs[1]) || !cJSON_IsNumber(objs[2]) || !cJSON_IsNumber(objs[3]))
        return ws_reply_status(server, frame, "lcd_update_config", -2);
    uint8_t enable = cJSON_IsTrue(objs[0]);
    uint8_t brightness = objs[1]->valueint;
    uint8_t min = objs[2]->valueint;
    uint8_t max = objs[3]->valueint;
    if (max > 100)
        max = 100;
    if (min > 100)
        min = 0;
    if (objs[4]->valueint > 10 && lcd_saver_time != objs[4]->valueint * 1000)
        lcd_saver_time = objs[4]->valueint * 1000;
    lcd_bri_on(enable);
    lcd_set_bri(brightness);
    if (lcd_state.lcd_bri_min != min)
    {
        config_set("lcd_bri_min", 1, &min);
        lcd_state.lcd_bri_min = min;
    }
    if (lcd_state.lcd_bri_max != max)
    {
        config_set("lcd_bri_max", 1, &max);
        lcd_state.lcd_bri_max = max;
    }
    return ws_reply_status(server, frame, "lcd_update_config", 0);
}

void z_lcd_api_init(void)
{
    uint8_t tmp = 0;
    if (config_get("lcd_bri_min", &tmp, 1))
        lcd_state.lcd_bri_min = tmp;
    if (config_get("lcd_bri_min", &tmp, 1))
        lcd_state.lcd_bri_max = tmp;
    else
        lcd_state.lcd_bri_max = 100;
    tmp = 4;
    config_get("lcd_bri", &tmp, 1);
    void *disp = lcd_init();
    if (!disp)
        return;
    lcd_bri_init();
    lcd_bri_on(1);
    lcd_set_bri(tmp);
    ADD_API_ITEM(lcd_update_config);
    ADD_API_ITEM(lcd_get_config);
}
ADD_API(z_lcd_api_init)
